﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using yafc.Interface.Api;
using System.Net;
using yafc.Data;
using System.Threading;

namespace yafc.Activity
{
	public delegate void ApiEvent(Account Acc, ApiResults R);

	public class Account : IDisposable
	{
		public event ApiEvent Timeline;
		public event ApiEvent LoginResults;
		public event ApiEvent SendResults;

		private bool logged = false;

		private string username;
		private string password;
		private string servname;

		private IFactory factory;
		private IApi[] apis;

		private int usageCount = 0;

		private Post max = null;

		private class Dominator
		{
			private Session sess = null;
			public Session Sess
			{
				get { return sess; }
				set { sess = value; }
			}
		}
		private Dominator dominator = new Dominator();				// Update only issues from dominator.

		private Dictionary<string, Post> posts = new Dictionary<string, Post>();

		public bool Logged
		{
			get { return logged; }
		}
		public string Username
		{
			get { return username; }
			protected set 
			{ 
				username = value;
			}
		}
		public string Password
		{
			get { return decrypt(password); }
			protected set 
			{ 
				password = encrypt(value);
			}
		}
		public string Servname
		{
			get { return servname; }
		}
		protected int UsageCount
		{
			get { return usageCount; }
		}

		protected IFactory Factory
		{
			get { return factory; }
			set {
				factory = value;
				if (factory != null)
				{
					apis = new IApi[(int)ApiType.Max];
					if (apis != null)
					{
						for (int i = 0; i < apis.Length; ++i )
						{
							apis[i] = Factory.create((ApiType)i);
						}
					}
				}
			}
		}

		private Account(IFactory f, string serv, string name, string pwd)
		{
			servname = serv;
			Username = name;
			Password = pwd;
			Factory = f;
		}

		public bool IsDominated
		{
			get { 
				lock(dominator)
					{ return dominator.Sess != null; }
			}
		}
		public bool TryDominate(Session s)
		{
			lock(dominator)
			{
				if (dominator.Sess == null)
				{
					dominator.Sess = s;
					Interlocked.Increment(ref usageCount);
					return true;
				}
				else
				{
					Interlocked.Increment(ref usageCount);
					return false;
				}
			}
		}
		public bool TryFree(Session s)
		{
			lock (dominator)
			{
				if (dominator.Sess == s)
				{
					dominator.Sess = null;
					Interlocked.Decrement(ref usageCount);
					return true;
				}
			}
			Interlocked.Decrement(ref usageCount);
			return false;
		}
		public bool IsDominatedBy(Session s)
		{
			lock(dominator)
				{ return dominator.Sess == s; } 
		}

		public bool Equals(string serv, string name)
		{
			return Servname == serv && Username == name;
		}

		public void Login()
		{
			var la = new LoginArgs(Username, Password);
			IApi call = api(ApiType.Login);
			OnLoginResults(call.execute(la));
		}
		public void Relogin(string Uname, string Passwd)
		{
			Username = Uname;
			Password = Passwd;
			Login();
		}
		public void Send(string Text)
		{
			SendArgs sa = new SendArgs(Username, Password, Text);
			IApi call = api(ApiType.PostMsg);
			OnSendResults(call.execute(sa));
		}
		public void FetchTimeline(Session S)
		{
			if (IsDominatedBy(S))
			{
				string maxId = null;
				if (max != null)
				{
					maxId = max.id;
				}
				var ta = new TLArgs(Username, Password, maxId);
				OnTimeline(api(ApiType.Timeline).execute(ta));
			}
		}

		protected virtual void OnTimeline(ApiResults r)
		{
			if (r.status == HttpStatusCode.Unauthorized)
			{
				r.username = Username;
				r.password = Password;
			}
			else if (r.status == HttpStatusCode.OK)
			{
				TLResults tlr = r as TLResults;
				if (tlr != null)
				{
					lock (posts)
					{
						if (max == null && tlr.posts.Count() > 0)
						{
							max = tlr.posts[0];
						}
						foreach (var p in tlr.posts)
						{
							if (!posts.ContainsKey(p.id))
							{
								if (max.timestamp < p.timestamp)
								{
									max = p;
								}
								posts.Add(p.id, p);
							}
						}
					}
				}
			}

			var temp = Interlocked.CompareExchange(ref Timeline, null, null);
			if (temp != null)
				temp(this, r);
		}
		protected virtual void OnLoginResults(ApiResults r)
		{
			r.service = Servname;
			r.username = Username;
			if (r.status == HttpStatusCode.OK)
			{
				logged = true;
			}
			else
			{
				r.password = Password;
			}
			var temp = Interlocked.CompareExchange(ref LoginResults, null, null);
			if (temp != null)
				temp(this, r);
		}
		protected virtual void OnSendResults(ApiResults r)
		{
			var temp = Interlocked.CompareExchange(ref SendResults, null, null);
			if (temp != null)
				temp(this, r);
		}

		public virtual void Dispose()
		{
			Dispose(true);
		}
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				Remove(this);
			}
		}

		protected virtual string encrypt(string value)
		{
			return value;
		}
		protected virtual string decrypt(string value)
		{
			return value;
		}

		protected IApi api(ApiType T)
		{
			if (apis != null)
			{
				return apis[(int)T];
			}
			return null;
		}

		// --------------------------------------------------------- //

		static private Dictionary<string, List<Account>> services = new Dictionary<string, List<Account>>();

		static public Account Create(IFactory f, string serv, string name, string pwd)
		{
			List<Account> acclist = null;
			lock(services)
			{
				if (!services.TryGetValue(serv, out acclist))
				{
					acclist = new List<Account>();
					services.Add(serv, acclist);
				}
			}
			lock (acclist)
			{
				foreach (var acc in acclist)
				{
					if (acc.Username == name)
					{
						return acc;
					}
				}
				var newAcc = new Account(f, serv, name, pwd);
				acclist.Add(newAcc);
				return newAcc;
			}
		}
		static public bool Contains(string serv, string name)
		{
			List<Account> acclist = null;
			lock(services)
			{
				if (!services.TryGetValue(serv, out acclist))
				{
					return false;
				}
			}
			lock(acclist)
			{
				foreach (var acc in acclist)
				{
					if (acc.Username == name)
					{
						return true;
					}
				}
			}
			return false;
		}
		static public void Remove(Account acc)
		{
			List<Account> acclist = null;
			lock(services)
			{
				if (!services.TryGetValue(acc.Servname, out acclist))
				{
					throw new Exception("Fatal Error: Account not in Account Manager!");
				}
			}
			if (acc.UsageCount == 0)
			{
				lock (acclist)
				{
					acclist.Remove(acc);
				}
			}
		}
		static public void Remove(string serv, string name)
		{
			List<Account> acclist = null;
			lock (services)
			{
				if (!services.TryGetValue(serv, out acclist))
				{
					throw new Exception("Fatal Error: Account not in Account Manager!");
				}
			}
			lock (acclist)
			{
				foreach (var acc in acclist)
				{
					if (acc.Username == name)
					{
						acclist.Remove(acc);
						return;
					}
				}
			}
		}

		static ICollection<string> Services()
		{
			return services.Keys;
		}
		static ICollection<Account> AccountsOf(string serv)
		{
			List<Account> acclist = null;
			if (services.TryGetValue(serv, out acclist))
			{
				return acclist;
			}
			return new Account[0];
		}
	}
}
